/*
* UAE - The Un*x Amiga Emulator
*
* lowlevel cd device glue, scsi emulator
*
* Copyright 2009-2010 Toni Wilen
*
*/

#include "sysconfig.h"
#include "sysdeps.h"
#include "options.h"
#include "memory.h"

#include "blkdev.h"
#include "scsidev.h"
#include "savestate.h"
#include "crc32.h"
#include "threaddep/thread.h"
#include "execio.h"
#ifdef RETROPLATFORM
#include "rp.h"
#endif

int log_scsiemu = 0;

#define PRE_INSERT_DELAY (3 * (currprefs.ntscmode ? 60 : 50))

static int scsiemu[MAX_TOTAL_SCSI_DEVICES];

static struct device_functions *device_func[MAX_TOTAL_SCSI_DEVICES];
static int openlist[MAX_TOTAL_SCSI_DEVICES];
static int waspaused[MAX_TOTAL_SCSI_DEVICES];
static int delayed[MAX_TOTAL_SCSI_DEVICES];
static uae_sem_t unitsem[MAX_TOTAL_SCSI_DEVICES];
static int unitsem_cnt[MAX_TOTAL_SCSI_DEVICES];

static int play_end_pos[MAX_TOTAL_SCSI_DEVICES];
static uae_u8 play_qcode[MAX_TOTAL_SCSI_DEVICES][SUBQ_SIZE];

static TCHAR newimagefiles[MAX_TOTAL_SCSI_DEVICES][256];
static int imagechangetime[MAX_TOTAL_SCSI_DEVICES];
static bool cdimagefileinuse[MAX_TOTAL_SCSI_DEVICES], wasopen[MAX_TOTAL_SCSI_DEVICES];

/* convert minutes, seconds and frames -> logical sector number */
int msf2lsn (int msf)
{
	int sector = (((msf >> 16) & 0xff) * 60 * 75 + ((msf >> 8) & 0xff) * 75 + ((msf >> 0) & 0xff));
	sector -= 150;
	return sector;
}

/* convert logical sector number -> minutes, seconds and frames */
int lsn2msf (int sectors)
{
	int msf;
	sectors += 150;
	msf = (sectors / (75 * 60)) << 16;
	msf |= ((sectors / 75) % 60) << 8;
	msf |= (sectors % 75) << 0;
	return msf;
}

uae_u8 frombcd (uae_u8 v)
{
	return (v >> 4) * 10 + (v & 15);
}
uae_u8 tobcd (uae_u8 v)
{
	return ((v / 10) << 4) | (v % 10);
}
int fromlongbcd (uae_u8 *p)
{
	return (frombcd (p[0]) << 16) | (frombcd (p[1]) << 8) | (frombcd (p[2])  << 0);
}
void tolongbcd (uae_u8 *p, int v)
{
	p[0] = tobcd ((v >> 16) & 0xff);
	p[1] = tobcd ((v >> 8) & 0xff);
	p[2] = tobcd ((v >> 0) & 0xff);
}

static struct cd_toc *gettoc (struct cd_toc_head *th, int block)
{
	for (int i = th->first_track_offset; i < th->last_track_offset; i++) {
		struct cd_toc *t = &th->toc[i];
		if (block < t->paddress) {
			if (i == th->first_track_offset)
				return t;
			else
				return t - 1;
		}
	}
	return &th->toc[th->last_track_offset];
}

int isaudiotrack (struct cd_toc_head *th, int block)
{
	struct cd_toc *t = gettoc (th, block);
	if (!t)
		return 0;
	return (t->control & 0x0c) != 4;
}
int isdatatrack (struct cd_toc_head *th, int block)
{
	return !isaudiotrack (th, block);
}

static int cdscsidevicetype[MAX_TOTAL_SCSI_DEVICES];

#ifdef _WIN32

#include "od-win32/win32.h"

extern struct device_functions devicefunc_win32_aspi;
extern struct device_functions devicefunc_win32_spti;
extern struct device_functions devicefunc_win32_ioctl;
extern struct device_functions devicefunc_cdimage;

static struct device_functions *devicetable[] = {
	NULL,
	&devicefunc_cdimage,
	&devicefunc_win32_ioctl,
	&devicefunc_win32_spti,
	&devicefunc_win32_aspi,
	NULL
};
static int driver_installed[6];

static void install_driver (int flags)
{
	for (int i = 0; i < MAX_TOTAL_SCSI_DEVICES; i++) {
		scsiemu[i] = false;
		device_func[i] = NULL;
	}
	if (flags > 0) {
		device_func[0] = devicetable[flags];
		scsiemu[0] = true;
	} else {
		for (int i = 0; i < MAX_TOTAL_SCSI_DEVICES; i++) {
			scsiemu[i] = false;
			device_func[i] = NULL;
			switch (cdscsidevicetype[i])
			{
				case SCSI_UNIT_IMAGE:
				device_func[i] = devicetable[SCSI_UNIT_IMAGE];
				scsiemu[i] = true;
				break;
				case SCSI_UNIT_IOCTL:
				device_func[i] = devicetable[SCSI_UNIT_IOCTL];
				scsiemu[i] = true;
				break;
				case SCSI_UNIT_SPTI:
				if (currprefs.win32_uaescsimode == UAESCSI_CDEMU) {
					device_func[i] = devicetable[SCSI_UNIT_IOCTL];
					scsiemu[i] = true;
				} else {
					device_func[i] = devicetable[SCSI_UNIT_SPTI];
				}
				break;
				case SCSI_UNIT_ASPI:
				device_func[i] = devicetable[SCSI_UNIT_ASPI];
				break;
			}
		}
	}

	for (int j = 1; devicetable[j]; j++) {
		if (!driver_installed[j]) {
			for (int i = 0; i < MAX_TOTAL_SCSI_DEVICES; i++) {
				if (device_func[i] == devicetable[j]) {
					int ok = device_func[i]->openbus (0);
					driver_installed[j] = 1;
					write_log (L"%s driver installed, ok=%d\n", device_func[i]->name, ok);
					break;
				}
			}
		}
	}

}
#endif

void blkdev_default_prefs (struct uae_prefs *p)
{
	for (int i = 0; i < MAX_TOTAL_SCSI_DEVICES; i++) {
		p->cdslots[i].name[0] = 0;
		p->cdslots[i].inuse = false;
		p->cdslots[i].type = SCSI_UNIT_DEFAULT;
		cdscsidevicetype[i] = SCSI_UNIT_DEFAULT;
	}
}

void blkdev_fix_prefs (struct uae_prefs *p)
{
	for (int i = 0; i < MAX_TOTAL_SCSI_DEVICES; i++) {
		cdscsidevicetype[i] = p->cdslots[i].type;
		if (p->cdslots[i].inuse == false && p->cdslots[i].name[0] && p->cdslots[i].type != SCSI_UNIT_DISABLED)
			p->cdslots[i].inuse = true;
	}

	// blkdev_win32_aspi.cpp does not support multi units
	if (currprefs.win32_uaescsimode >= UAESCSI_ASPI_FIRST) {
		for (int i = 0; i < MAX_TOTAL_SCSI_DEVICES; i++) {
			if (cdscsidevicetype[i] != SCSI_UNIT_DISABLED)
				cdscsidevicetype[i] = SCSI_UNIT_ASPI;
		}
		return;
	}

	for (int i = 0; i < MAX_TOTAL_SCSI_DEVICES; i++) {
		if (cdscsidevicetype[i] != SCSI_UNIT_DEFAULT)
			continue;
		if (p->cdslots[i].inuse || p->cdslots[i].name[0]) {
			TCHAR *name = p->cdslots[i].name;
			if (_tcslen (name) == 3 && name[1] == ':' && name[2] == '\\') {
				if (currprefs.scsi && (currprefs.win32_uaescsimode == UAESCSI_SPTI || currprefs.win32_uaescsimode == UAESCSI_SPTISCAN))
					cdscsidevicetype[i] = SCSI_UNIT_SPTI;
				else
					cdscsidevicetype[i] = SCSI_UNIT_IOCTL;
			} else {
				cdscsidevicetype[i] = SCSI_UNIT_IMAGE;
			}
		} else if (currprefs.scsi) {
			if (currprefs.win32_uaescsimode == UAESCSI_CDEMU)
				cdscsidevicetype[i] = SCSI_UNIT_IOCTL;
			else if (currprefs.win32_uaescsimode >= UAESCSI_ASPI_FIRST)
				cdscsidevicetype[i] = SCSI_UNIT_ASPI;
			else
				cdscsidevicetype[i] = SCSI_UNIT_SPTI;
		} else {
			cdscsidevicetype[i] = SCSI_UNIT_IOCTL;
		}
	}

}

static bool getsem (int unitnum, bool dowait)
{
	if (unitsem[unitnum] == NULL)
		uae_sem_init (&unitsem[unitnum], 0, 1);
	bool gotit = false;
	if (dowait) {
		uae_sem_wait (&unitsem[unitnum]);
		gotit = true;
	} else {
		gotit = uae_sem_trywait (&unitsem[unitnum]) == 0;
	}
	if (gotit)
		unitsem_cnt[unitnum]++;
	if (unitsem_cnt[unitnum] > 1)
		write_log (L"CD: unitsem%d acquire mismatch! cnt=%d\n", unitnum, unitsem_cnt[unitnum]);
	return gotit;
}
static bool getsem (int unitnum)
{
	return getsem (unitnum, false);
}
static void freesem (int unitnum)
{
	unitsem_cnt[unitnum]--;
	if (unitsem_cnt[unitnum] < 0)
		write_log (L"CD: unitsem%d release mismatch! cnt=%d\n", unitnum, unitsem_cnt[unitnum]);
	uae_sem_post (&unitsem[unitnum]);
}
static void sys_command_close_internal (int unitnum)
{
	getsem (unitnum, true);
	waspaused[unitnum] = 0;
	if (openlist[unitnum] <= 0)
		write_log (L"BUG unit %d close: opencnt=%d!\n", unitnum, openlist[unitnum]);
	if (device_func[unitnum]) {
		device_func[unitnum]->closedev (unitnum);
		if (openlist[unitnum] > 0)
			openlist[unitnum]--;
	}
	freesem (unitnum);
	if (openlist[unitnum] == 0) {
		uae_sem_destroy (&unitsem[unitnum]);
		unitsem[unitnum] = NULL;
	}
}

static int sys_command_open_internal (int unitnum, const TCHAR *ident, cd_standard_unit csu)
{
	int ret = 0;
	if (unitsem[unitnum] == NULL)
		uae_sem_init (&unitsem[unitnum], 0, 1);
	getsem (unitnum, true);
	if (openlist[unitnum])
		write_log (L"BUG unit %d open: opencnt=%d!\n", unitnum, openlist[unitnum]);
	if (device_func[unitnum]) {
		ret = device_func[unitnum]->opendev (unitnum, ident, csu != CD_STANDARD_UNIT_DEFAULT);
		if (ret)
			openlist[unitnum]++;
	}
	freesem (unitnum);
	return ret;
}

static int getunitinfo (int unitnum, int drive, cd_standard_unit csu, int *isaudio)
{
	struct device_info di;
	if (sys_command_info (unitnum, &di, 0)) {
		write_log (L"Scanning drive %s: ", di.label);
		if (di.media_inserted) {
			if (isaudiotrack (&di.toc, 0)) {
				if (*isaudio == 0)
					*isaudio = drive;
				write_log (L"CDA");
			}
			uae_u8 buffer[2048];
			if (sys_command_cd_read (unitnum, buffer, 16, 1)) {
				if (!memcmp (buffer + 8, "CDTV", 4) || !memcmp (buffer + 8, "CD32", 4) || !memcmp (buffer + 8, "COMM", 4)) {
					uae_u32 crc;
					write_log (L"CD32 or CDTV");
					if (sys_command_cd_read (unitnum, buffer, 21, 1)) {
						crc = get_crc32 (buffer, sizeof buffer);
						if (crc == 0xe56c340f) {
							write_log (L" [CD32.TM]");
							if (csu == CD_STANDARD_UNIT_CD32) {
								write_log (L"\n");
								return 1;
							}
						}
					}
					if (csu == CD_STANDARD_UNIT_CDTV || csu == CD_STANDARD_UNIT_CD32) {
						write_log (L"\n");
						return 1;
					}
				}
			}
		} else {
			write_log (L"no media");
		}
	}
	write_log (L"\n");
	return 0;
}

static int get_standard_cd_unit2 (cd_standard_unit csu)
{
	int unitnum = 0;
	int isaudio = 0;
	if (currprefs.cdslots[unitnum].name[0] || currprefs.cdslots[unitnum].inuse) {
		if (currprefs.cdslots[unitnum].name[0]) {
			device_func_init (SCSI_UNIT_IOCTL);
			if (!sys_command_open_internal (unitnum, currprefs.cdslots[unitnum].name, csu)) {
				device_func_init (SCSI_UNIT_IMAGE);
				if (!sys_command_open_internal (unitnum, currprefs.cdslots[unitnum].name, csu))
					goto fallback;
			}
		} else {
			goto fallback;
		}
		return unitnum;
	}
	device_func_init (SCSI_UNIT_IOCTL);
	for (int drive = 'C'; drive <= 'Z'; ++drive) {
		TCHAR vol[100];
		_stprintf (vol, L"%c:\\", drive);
		int drivetype = GetDriveType (vol);
		if (drivetype == DRIVE_CDROM) {
			if (sys_command_open_internal (unitnum, vol, csu)) {
				if (getunitinfo (unitnum, drive, csu, &isaudio))
					return unitnum;
				sys_command_close (unitnum);
			}
		}
	}
	if (isaudio) {
		TCHAR vol[100];
		_stprintf (vol, L"%c:\\", isaudio);
		if (sys_command_open_internal (unitnum, vol, csu)) 
			return unitnum;
	}
fallback:
	device_func_init (SCSI_UNIT_IMAGE);
	if (!sys_command_open_internal (unitnum, L"", csu)) {
		write_log (L"image mounter failed to open as empty!?\n");
		return -1;
	}
	return unitnum;
}

int get_standard_cd_unit (cd_standard_unit csu)
{
	int unitnum = get_standard_cd_unit2 (csu);
	if (unitnum < 0)
		return -1;
#ifdef RETROPLATFORM
	rp_cd_device_enable (unitnum, true);
#endif
	delayed[unitnum] = 0;
	if (currprefs.cdslots[unitnum].delayed) {
		delayed[unitnum] = PRE_INSERT_DELAY;
	}
	return unitnum;
}

void close_standard_cd_unit (int unitnum)
{
	sys_command_close (unitnum);
}

int sys_command_isopen (int unitnum)
{
	return openlist[unitnum];
}

int sys_command_open (int unitnum)
{
	waspaused[unitnum] = 0;
	int v = sys_command_open_internal (unitnum, currprefs.cdslots[unitnum].name[0] ? currprefs.cdslots[unitnum].name : NULL, CD_STANDARD_UNIT_DEFAULT);
	if (!v)
		return 0;
#ifdef RETROPLATFORM
	rp_cd_device_enable (unitnum, true);
#endif
	return v;
}

void sys_command_close (int unitnum)
{
#ifdef RETROPLATFORM
	rp_cd_device_enable (unitnum, false);
#endif
	sys_command_close_internal (unitnum);
}

void blkdev_cd_change (int unitnum, const TCHAR *name)
{
	struct device_info di;
	sys_command_info (unitnum, &di, 1);
#ifdef RETROPLATFORM
	rp_cd_image_change (unitnum, name);
#endif
}

void device_func_reset (void)
{
	for (int i = 0; i < MAX_TOTAL_SCSI_DEVICES; i++) {
		wasopen[i] = false;
		waspaused[i] = false;
		imagechangetime[i] = 0;
		cdimagefileinuse[i] = false;
		newimagefiles[i][0] = 0;
	}
}

int device_func_init (int flags)
{
	blkdev_fix_prefs (&currprefs);
	install_driver (flags);
	return 1;
}

void blkdev_entergui (void)
{
	for (int i = 0; i < MAX_TOTAL_SCSI_DEVICES; i++) {
		waspaused[i] = 0;
		struct device_info di;
		if (sys_command_info (i, &di, 1)) {
			if (sys_command_cd_pause (i, 1) == 0)
				waspaused[i] = 1;
		}
	}
}
void blkdev_exitgui (void)
{
	for (int i = 0; i < MAX_TOTAL_SCSI_DEVICES; i++) {
		if (waspaused[i]) {
			struct device_info di;
			if (sys_command_info (i, &di, 1)) {
				sys_command_cd_pause (i, 0);
			}
		}
		waspaused[i] = 0;
	}
}

static void check_changes (int unitnum)
{
	bool changed = false;

	if (device_func[unitnum] == NULL)
		return;

	if (delayed[unitnum]) {
		delayed[unitnum]--;
		if (delayed[unitnum] == 0)
			write_log (L"CD: startup delayed insert '%s'\n", currprefs.cdslots[unitnum].name[0] ? currprefs.cdslots[unitnum].name : L"<EMPTY>");
		return;
	}

	if (_tcscmp (changed_prefs.cdslots[unitnum].name, currprefs.cdslots[unitnum].name) != 0)
		changed = true;
	if (!changed && changed_prefs.cdslots[unitnum].name[0] == 0 && changed_prefs.cdslots[unitnum].inuse != currprefs.cdslots[unitnum].inuse)
		changed = true;

	if (changed) {
		if (unitsem[unitnum])
			getsem (unitnum, true);
		cdimagefileinuse[unitnum] = changed_prefs.cdslots[unitnum].inuse;
		_tcscpy (newimagefiles[unitnum], changed_prefs.cdslots[unitnum].name);
		changed_prefs.cdslots[unitnum].name[0] = currprefs.cdslots[unitnum].name[0] = 0;
		currprefs.cdslots[unitnum].inuse = changed_prefs.cdslots[unitnum].inuse;
		int pollmode = 0;
		imagechangetime[unitnum] = 3 * 50;
		struct device_info di;
		device_func[unitnum]->info (unitnum, &di, 0);
		wasopen[unitnum] = di.open;
		if (wasopen[unitnum]) {
			device_func[unitnum]->closedev (unitnum);
			if (currprefs.scsi)  {
				scsi_do_disk_change (unitnum, 0, &pollmode);
				if (pollmode)
					imagechangetime[unitnum] = 8 * 50;
			}
		}
		write_log (L"CD: eject (%s)\n", pollmode ? L"slow" : L"fast");
#ifdef RETROPLATFORM
		rp_cd_image_change (unitnum, NULL); 
#endif
		if (unitsem[unitnum])
			freesem (unitnum);
	}
	if (imagechangetime[unitnum] == 0)
		return;
	imagechangetime[unitnum]--;
	if (imagechangetime[unitnum] > 0)
		return;
	if (unitsem[unitnum])
		getsem (unitnum, true);
	_tcscpy (currprefs.cdslots[unitnum].name, newimagefiles[unitnum]);
	_tcscpy (changed_prefs.cdslots[unitnum].name, newimagefiles[unitnum]);
	currprefs.cdslots[unitnum].inuse = changed_prefs.cdslots[unitnum].inuse = cdimagefileinuse[unitnum];
	newimagefiles[unitnum][0] = 0;
	write_log (L"CD: delayed insert '%s'\n", currprefs.cdslots[unitnum].name[0] ? currprefs.cdslots[unitnum].name : L"<EMPTY>");
	device_func_init (0);
	if (wasopen[unitnum]) {
		if (!device_func[unitnum]->opendev (unitnum, currprefs.cdslots[unitnum].name, 0)) {
			write_log (L"-> device open failed\n");
		}
	}
	wasopen[unitnum] = 0;
	if (currprefs.scsi) {
		struct device_info di;
		device_func[unitnum]->info (unitnum, &di, 0);
		int pollmode;
		scsi_do_disk_change (unitnum, 1, &pollmode);
	}
#ifdef RETROPLATFORM
	rp_cd_image_change (unitnum, currprefs.cdslots[unitnum].name);
#endif
	if (unitsem[unitnum])
		freesem (unitnum);

	config_changed = 1;

}

void blkdev_vsync (void)
{
	for (int i = 0; i < MAX_TOTAL_SCSI_DEVICES; i++)
		check_changes (i);
}

static int do_scsi (int unitnum, uae_u8 *cmd, int cmdlen)
{
	uae_u8 *p = device_func[unitnum]->exec_out (unitnum, cmd, cmdlen);
	return p != NULL;
}
static int do_scsi (int unitnum, uae_u8 *cmd, int cmdlen, uae_u8 *out, int outsize)
{
	uae_u8 *p = device_func[unitnum]->exec_in (unitnum, cmd, cmdlen, &outsize);
	if (p)
		memcpy (out, p, outsize);
	return p != NULL;
}

static int failunit (int unitnum)
{
	if (unitnum < 0 || unitnum >= MAX_TOTAL_SCSI_DEVICES)
		return 1;
	if (device_func[unitnum] == NULL)
		return 1;
	return 0;
}

static int audiostatus (int unitnum)
{
	if (!getsem (unitnum))
		return 0;
	uae_u8 cmd[10] = {0x42,2,0x40,1,0,0,0,(uae_u8)(DEVICE_SCSI_BUFSIZE>>8),(uae_u8)(DEVICE_SCSI_BUFSIZE&0xff),0};
	uae_u8 *p = device_func[unitnum]->exec_in (unitnum, cmd, sizeof (cmd), 0);
	freesem (unitnum);
	if (!p)
		return 0;
	return p[1];
}

/* pause/unpause CD audio */
int sys_command_cd_pause (int unitnum, int paused)
{
	if (failunit (unitnum))
		return -1;
	if (!getsem (unitnum))
		return 0;
	int v;
	if (device_func[unitnum]->pause == NULL) {
		int as = audiostatus (unitnum);
		uae_u8 cmd[10] = {0x4b,0,0,0,0,0,0,0,paused?0:1,0};
		do_scsi (unitnum, cmd, sizeof cmd);
		v = as == AUDIO_STATUS_PAUSED;
	} else {
		v = device_func[unitnum]->pause (unitnum, paused);
	}
	freesem (unitnum);
	return v;
}

/* stop CD audio */
void sys_command_cd_stop (int unitnum)
{
	if (failunit (unitnum))
		return;
	if (!getsem (unitnum))
		return;
	if (device_func[unitnum]->stop == NULL) {
		int as = audiostatus (unitnum);
		uae_u8 cmd[6] = {0x4e,0,0,0,0,0};
		do_scsi (unitnum, cmd, sizeof cmd);
	} else {
		device_func[unitnum]->stop (unitnum);
	}
	freesem (unitnum);
}

/* play CD audio */
int sys_command_cd_play (int unitnum, int startlsn, int endlsn, int scan)
{
	int v;
	if (failunit (unitnum))
		return 0;
	if (!getsem (unitnum))
		return 0;
	play_end_pos[unitnum] = endlsn;
	if (device_func[unitnum]->play == NULL) {
		uae_u8 cmd[12] = {0,0,0,0,0,0,0,0,0,0,0,0};
		int startmsf = lsn2msf (startlsn);
		int endmsf = lsn2msf (endlsn);
		cmd[0] = 0x47;
		cmd[3] = (uae_u8)(startmsf >> 16);
		cmd[4] = (uae_u8)(startmsf >> 8);
		cmd[5] = (uae_u8)(startmsf >> 0);
		cmd[6] = (uae_u8)(endmsf >> 16);
		cmd[7] = (uae_u8)(endmsf >> 8);
		cmd[8] = (uae_u8)(endmsf >> 0);
		v = do_scsi (unitnum, cmd, sizeof cmd) ? 0 : 1;
	} else {
		v = device_func[unitnum]->play (unitnum, startlsn, endlsn, scan, NULL, NULL);
	}
	freesem (unitnum);
	return v;
}

/* play CD audio with subchannels */
int sys_command_cd_play (int unitnum, int startlsn, int endlsn, int scan, play_status_callback statusfunc, play_subchannel_callback subfunc)
{
	int v;
	if (failunit (unitnum))
		return 0;
	if (!getsem (unitnum))
		return 0;
	if (device_func[unitnum]->play == NULL)
		v = sys_command_cd_play (unitnum, startlsn, endlsn, scan);
	else
		v = device_func[unitnum]->play (unitnum, startlsn, endlsn, scan, statusfunc, subfunc);
	freesem (unitnum);
	return v;
}

/* set CD audio volume */
uae_u32 sys_command_cd_volume (int unitnum, uae_u16 volume_left, uae_u16 volume_right)
{
	int v;
	if (failunit (unitnum))
		return 0;
	if (!getsem (unitnum))
		return 0;
	if (device_func[unitnum]->volume == NULL)
		v = -1;
	else
		v = device_func[unitnum]->volume (unitnum, volume_left, volume_right);
	freesem (unitnum);
	return v;
}

/* read qcode */
int sys_command_cd_qcode (int unitnum, uae_u8 *buf)
{
	int v;
	if (failunit (unitnum))
		return 0;
	if (!getsem (unitnum))
		return 0;
	if (device_func[unitnum]->qcode == NULL) {
		uae_u8 cmd[10] = {0x42,2,0x40,1,0,0,0,(uae_u8)(SUBQ_SIZE>>8),(uae_u8)(SUBQ_SIZE&0xff),0};
		v = do_scsi (unitnum, cmd, sizeof cmd, buf, SUBQ_SIZE);
	} else {
		v = device_func[unitnum]->qcode (unitnum, buf, -1);
	}
	freesem (unitnum);
	return v;
};

/* read table of contents */
int sys_command_cd_toc (int unitnum, struct cd_toc_head *toc)
{
	int v;
	if (failunit (unitnum))
		return 0;
	if (!getsem (unitnum))
		return 0;
	if (device_func[unitnum]->toc == NULL) {
		uae_u8 buf[4 + 8 * 103];
		int size = sizeof buf;
		uae_u8 cmd [10] = { 0x43,0,2,0,0,0,0,(uae_u8)(size>>8),(uae_u8)(size&0xff),0};
		if (do_scsi (unitnum, cmd, sizeof cmd, buf, size)) {
			// toc parse to do
			v = 0;
		}
		v = 0;
	} else {
		v = device_func[unitnum]->toc (unitnum, toc);
	}
	freesem (unitnum);
	return v;
}

/* read one cd sector */
int sys_command_cd_read (int unitnum, uae_u8 *data, int block, int size)
{
	int v;
	if (failunit (unitnum))
		return 0;
	if (!getsem (unitnum))
		return 0;
	if (device_func[unitnum]->read == NULL) {
		uae_u8 cmd[12] = { 0xbe, 0, block >> 24, block >> 16, block >> 8, block >> 0, size >> 16, size >> 8, size >> 0, 0x10, 0, 0 };
		v = do_scsi (unitnum, cmd, sizeof cmd, data, size * 2048);
	} else {
		v = device_func[unitnum]->read (unitnum, data, block, size);
	}
	freesem (unitnum);
	return v;
}
int sys_command_cd_rawread (int unitnum, uae_u8 *data, int block, int size, int sectorsize)
{
	int v;
	if (failunit (unitnum))
		return -1;
	if (!getsem (unitnum))
		return 0;
	if (device_func[unitnum]->rawread == NULL) {
		uae_u8 cmd[12] = { 0xbe, 0, block >> 24, block >> 16, block >> 8, block >> 0, size >> 16, size >> 8, size >> 0, 0x10, 0, 0 };
		v = do_scsi (unitnum, cmd, sizeof cmd, data, size * sectorsize);
	} else {
		v = device_func[unitnum]->rawread (unitnum, data, block, size, sectorsize, 0xffffffff);
	}
	freesem (unitnum);
	return v;
}
int sys_command_cd_rawread (int unitnum, uae_u8 *data, int block, int size, int sectorsize, uae_u8 sectortype, uae_u8 scsicmd9, uae_u8 subs)
{
	int v;
	if (failunit (unitnum))
		return -1;
	if (!getsem (unitnum))
		return 0;
	if (device_func[unitnum]->rawread == NULL) {
		uae_u8 cmd[12] = { 0xbe, 0, block >> 24, block >> 16, block >> 8, block >> 0, size >> 16, size >> 8, size >> 0, 0x10, 0, 0 };
		v = do_scsi (unitnum, cmd, sizeof cmd, data, size * sectorsize);
	} else {
		v = device_func[unitnum]->rawread (unitnum, data, block, size, sectorsize, (sectortype << 16) | (scsicmd9 << 8) | subs);
	}
	freesem (unitnum);
	return v;
}

/* read block */
int sys_command_read (int unitnum, uae_u8 *data, int block, int size)
{
	int v;
	if (failunit (unitnum))
		return 0;
	if (!getsem (unitnum))
		return 0;
	if (device_func[unitnum]->read == NULL) {
		uae_u8 cmd[12] = { 0xa8, 0, 0, 0, 0, 0, size >> 24, size >> 16, size >> 8, size >> 0, 0, 0 };
		cmd[2] = (uae_u8)(block >> 24);
		cmd[3] = (uae_u8)(block >> 16);
		cmd[4] = (uae_u8)(block >> 8);
		cmd[5] = (uae_u8)(block >> 0);
		v = do_scsi (unitnum, cmd, sizeof cmd, data, size * 2048);
	} else {
		v = device_func[unitnum]->read (unitnum, data, block, size);
	}
	freesem (unitnum);
	return v;
}

/* write block */
int sys_command_write (int unitnum, uae_u8 *data, int offset, int size)
{
	int v;
	if (failunit (unitnum))
		return 0;
	if (!getsem (unitnum))
		return 0;
	if (device_func[unitnum]->write == NULL) {
		v = 0;
	} else {
		v = device_func[unitnum]->write (unitnum, data, offset, size);
	}
	freesem (unitnum);
	return v;
}

int sys_command_ismedia (int unitnum, int quick)
{
	int v;
	if (failunit (unitnum))
		return -1;
	if (delayed[unitnum])
		return 0;
	if (!getsem (unitnum))
		return 0;
	if (device_func[unitnum] == NULL) {
		uae_u8 cmd[6] = { 0, 0, 0, 0, 0, 0 };
		v = do_scsi (unitnum, cmd, sizeof cmd);
	} else {
		v = device_func[unitnum]->ismedia (unitnum, quick);
	}
	freesem (unitnum);
	return v;
}

struct device_info *sys_command_info (int unitnum, struct device_info *di, int quick)
{
	if (failunit (unitnum))
		return NULL;
	if (!getsem (unitnum))
		return 0;
	struct device_info *di2 = device_func[unitnum]->info (unitnum, di, quick);
	if (di2 && delayed[unitnum])
		di2->media_inserted = 0;
	freesem (unitnum);
	return di2;
}

#define MODE_SELECT_6 0x15
#define MODE_SENSE_6 0x1a
#define MODE_SELECT_10 0x55
#define MODE_SENSE_10 0x5a

void scsi_atapi_fixup_pre (uae_u8 *scsi_cmd, int *len, uae_u8 **datap, int *datalenp, int *parm)
{
	uae_u8 cmd, *p, *data = *datap;
	int l, datalen = *datalenp;

	*parm = 0;
	cmd = scsi_cmd[0];
	if (cmd != MODE_SELECT_6 && cmd != MODE_SENSE_6)
		return;
	l = scsi_cmd[4];
	if (l > 4)
		l += 4;
	scsi_cmd[7] = l >> 8;
	scsi_cmd[8] = l;
	if (cmd == MODE_SELECT_6) {
		scsi_cmd[0] = MODE_SELECT_10;
		scsi_cmd[9] = scsi_cmd[5];
		scsi_cmd[2] = scsi_cmd[3] = scsi_cmd[4] = scsi_cmd[5] = scsi_cmd[6] = 0;
		*len = 10;
		p = xmalloc (uae_u8, 8 + datalen + 4);
		if (datalen > 4)
			memcpy (p + 8, data + 4, datalen - 4);
		p[0] = 0;
		p[1] = data[0];
		p[2] = data[1];
		p[3] = data[2];
		p[4] = p[5] = p[6] = 0;
		p[7] = data[3];
		if (l > 8)
			datalen += 4;
		*parm = MODE_SELECT_10;
		*datap = p;
	} else {
		scsi_cmd[0] = MODE_SENSE_10;
		scsi_cmd[9] = scsi_cmd[5];
		scsi_cmd[3] = scsi_cmd[4] = scsi_cmd[5] = scsi_cmd[6] = 0;
		if (l > 8)
			datalen += 4;
		*datap = xmalloc (uae_u8, datalen);
		*len = 10;
		*parm = MODE_SENSE_10;
	}
	*datalenp = datalen;
}

void scsi_atapi_fixup_post (uae_u8 *scsi_cmd, int len, uae_u8 *olddata, uae_u8 *data, int *datalenp, int parm)
{
	int datalen = *datalenp;
	if (!data || !datalen)
		return;
	if (parm == MODE_SENSE_10) {
		olddata[0] = data[1];
		olddata[1] = data[2];
		olddata[2] = data[3];
		olddata[3] = data[7];
		datalen -= 4;
		if (datalen > 4)
			memcpy (olddata + 4, data + 8, datalen - 4);
		*datalenp = datalen;
	}
}

static void scsi_atapi_fixup_inquiry (struct amigascsi *as)
{
	uae_u8 *scsi_data = as->data;
	uae_u32 scsi_len = as->len;
	uae_u8 *scsi_cmd = as->cmd;
	uae_u8 cmd;

	cmd = scsi_cmd[0];
	/* CDROM INQUIRY: most Amiga programs expect ANSI version == 2
	* (ATAPI normally responds with zero)
	*/
	if (cmd == 0x12 && scsi_len > 2 && scsi_data) {
		uae_u8 per = scsi_data[0];
		uae_u8 b = scsi_data[2];
		/* CDROM and ANSI version == 0 ? */
		if ((per & 31) == 5 && (b & 7) == 0) {
			b |= 2;
			scsi_data[2] = b;
		}
	}
}

void scsi_log_before (uae_u8 *cdb, int cdblen, uae_u8 *data, int datalen)
{
	int i;
	for (i = 0; i < cdblen; i++) {
		write_log (L"%s%02X", i > 0 ? L"." : L"", cdb[i]);
	}
	write_log (L"\n");
	if (data) {
		write_log (L"DATAOUT: %d\n", datalen);
		for (i = 0; i < datalen && i < 100; i++)
			write_log (L"%s%02X", i > 0 ? L"." : L"", data[i]);
		if (datalen > 0)
			write_log (L"\n");
	}
}

void scsi_log_after (uae_u8 *data, int datalen, uae_u8 *sense, int senselen)
{
	int i;
	write_log (L"DATAIN: %d\n", datalen);
	for (i = 0; i < datalen && i < 100 && data; i++)
		write_log (L"%s%02X", i > 0 ? L"." : L"", data[i]);
	if (data && datalen > 0)
		write_log (L"\n");
	if (senselen > 0) {
		write_log (L"SENSE: %d,", senselen);
		for (i = 0; i < senselen && i < 32; i++) {
			write_log (L"%s%02X", i > 0 ? L"." : L"", sense[i]);
		}
		write_log (L"\n");
	}
}

static bool nodisk (struct device_info *di)
{
	return di->media_inserted == 0;
}
static uae_u64 cmd_readx (int unitnum, uae_u8 *dataptr, int offset, int len)
{
	if (!getsem (unitnum))
		return 0;
	int v = device_func[unitnum]->read (unitnum, dataptr, offset, len);
	freesem (unitnum);
	if (v)
		return len;
	return 0;
}

static void wl (uae_u8 *p, int v)
{
	p[0] = v >> 24;
	p[1] = v >> 16;
	p[2] = v >> 8;
	p[3] = v;
}
static void ww (uae_u8 *p, int v)
{
	p[0] = v >> 8;
	p[1] = v;
}
static int rl (uae_u8 *p)
{
	return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | (p[3]);
}
static int rw (uae_u8 *p)
{
	return (p[0] << 8) | (p[1]);
}

static void stopplay (int unitnum)
{
	sys_command_cd_stop (unitnum);
}

static int addtocentry (uae_u8 **dstp, int *len, int point, int newpoint, int msf, uae_u8 *head, struct cd_toc_head *th)
{
	uae_u8 *dst = *dstp;

	for (int i = 0; i < th->points; i++) {
		struct cd_toc *t = &th->toc[i];
		if (t->point == point) {
			if (*len < 8)
				return 0;
			int addr = t->paddress;
			if (msf)
				addr = lsn2msf (addr);
			dst[0] = 0;
			dst[1] = (t->adr << 4) | t->control;
			dst[2] = newpoint >= 0 ? newpoint : point;
			dst[3] = 0;
			dst[4] = addr >> 24;
			dst[5] = addr >> 16;
			dst[6] = addr >>  8;
			dst[7] = addr >>  0;

			if (point >= 1 && point <= 99) {
				if (head[2] == 0)
					head[2] = point;
				head[3] = point;
			}

			*len -= 8;
			*dstp = dst + 8;
			return 1;
		}
	}
	return -1;
}

static int scsiemudrv (int unitnum, uae_u8 *cmd)
{
	if (failunit (unitnum))
		return -1;
	if (!getsem (unitnum))
		return 0;
	int v = 0;
	if (device_func[unitnum]->scsiemu)
		v = device_func[unitnum]->scsiemu (unitnum, cmd);
	freesem (unitnum);
	return v;
}

static int scsi_read_cd (int unitnum, uae_u8 *cmd, uae_u8 *data, struct device_info *di)
{
	int msf = cmd[0] == 0xb9;
	int start = msf ? msf2lsn (rl (cmd + 2) & 0x00ffffff) : rl (cmd + 2);
	int len = rl (cmd + 5) & 0x00ffffff;
	if (msf) {
		int end = msf2lsn (len);
		len = end - start;
		if (len < 0)
			return -1;
	}
	int subs = cmd[10] & 7;
	if (len == 0)
		return 0;
	return sys_command_cd_rawread (unitnum, data, start, len, 0, (cmd[1] >> 2) & 7, cmd[9], subs);
}

static int scsi_emulate (int unitnum, uae_u8 *cmdbuf, int scsi_cmd_len,
	uae_u8 *scsi_data, int *data_len, uae_u8 *r, int *reply_len, uae_u8 *s, int *sense_len)
{
	uae_u64 len, offset;
	int lr = 0, ls = 0;
	int scsi_len = -1;
	int status = 0;
	struct device_info di;
	uae_u8 cmd = cmdbuf[0];

	*reply_len = *sense_len = 0;
	memset (r, 0, 256);
	memset (s, 0, 256);

	sys_command_info (unitnum, &di, 1);

	if (cmdbuf[0] == 0) { /* TEST UNIT READY */
		if (nodisk (&di))
			goto nodisk;
		scsi_len = 0;
		goto end;
	}
	if (log_scsiemu)
		write_log (L"SCSIEMU %d: %02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X CMDLEN=%d DATA=%08X LEN=%d\n", unitnum,
			cmdbuf[0], cmdbuf[1], cmdbuf[2], cmdbuf[3], cmdbuf[4], cmdbuf[5], cmdbuf[6], 
			cmdbuf[7], cmdbuf[8], cmdbuf[9], cmdbuf[10], cmdbuf[11],
			scsi_cmd_len, scsi_data, *data_len);
	switch (cmdbuf[0])
	{
	case 0x12: /* INQUIRY */
	{
		if ((cmdbuf[1] & 1) || cmdbuf[2] != 0)
			goto err;
		len = cmdbuf[4];
		if (cmdbuf[1] >> 5)
			goto err;
		r[0] = 5; // CDROM
		r[1] |= 0x80; // removable
		r[2] = 2; /* supports SCSI-2 */
		r[3] = 2; /* response data format */
		r[4] = 32; /* additional length */
		r[7] = 0x20; /* 16 bit bus */
		scsi_len = lr = len < 36 ? (uae_u32)len : 36;
		r[2] = 2;
		r[3] = 2;
		char *s = ua (di.vendorid);
		memcpy (r + 8, s, strlen (s));
		xfree (s);
		s = ua (di.productid);
		memcpy (r + 16, s, strlen (s));
		xfree (s);
		s = ua (di.revision);
		memcpy (r + 32, s, strlen (s));
		xfree (s);
		for (int i = 8; i < 36; i++) {
			if (r[i] == 0)
				r[i] = 32;
		}
	}
	break;
	case 0xbe: // READ CD
	case 0xb9: // READ CD MSF
		if (nodisk (&di))
			goto nodisk;
		scsi_len = scsi_read_cd (unitnum, cmdbuf, scsi_data, &di);
		if (scsi_len == -2)
			goto notdatatrack;
		if (scsi_len == -1)
			goto errreq;
		break;
	case 0x55: // MODE SELECT(10)
	case 0x15: // MODE SELECT(6)
	{
		uae_u8 *p;
		p = scsi_data + 4;
		if (cmdbuf[0] == 0x55)
			p += 4;
		int pcode = p[0] & 0x3f;
		if (pcode == 14) { // CD audio control
			uae_u16 vol_left = (p[9] << 7) | (p[9] >> 1);
			uae_u16 vol_right = (p[11] << 7) | (p[11] >> 1);
			sys_command_cd_volume (unitnum, vol_left, vol_right);
			scsi_len = 0;
		} else {
			goto errreq;
		}
	}
	break;
	case 0x5a: // MODE SENSE(10)
	case 0x1a: /* MODE SENSE(6) */
	{
		uae_u8 *p;
		int pc = cmdbuf[2] >> 6;
		int pcode = cmdbuf[2] & 0x3f;
		int dbd = cmdbuf[1] & 8;
		if (cmdbuf[0] == 0x5a)
			dbd = 1;
		if (log_scsiemu)
			write_log (L"MODE SENSE PC=%d CODE=%d DBD=%d\n", pc, pcode, dbd);
		p = r;
		if (cmdbuf[0] == 0x5a) {
			p[0] = 8 - 1;
			p[1] = 0;
			p[2] = 0;
			p[3] = 0;
			p[4] = 0;
			p[5] = 0;
			p[6] = 0;
			p[7] = 0;
			p += 8;
		} else {
			p[0] = 4 - 1;
			p[1] = 0;
			p[2] = 0;
			p[3] = 0;
			p += 4;
		}
		if (!dbd) {
			if (nodisk (&di))
				goto nodisk;
			uae_u32 blocks = di.sectorspertrack * di.cylinders * di.trackspercylinder;
			p[-1] = 8;
			wl(p + 0, blocks);
			wl(p + 4, di.bytespersector);
			p += 8;
		}
		if (pcode == 0) {
			p[0] = 0;
			p[1] = 0;
			p[2] = 0x20;
			p[3] = 0;
			r[0] += 4;
		} else if (pcode == 3) {
			if (nodisk (&di))
				goto nodisk;
			p[0] = 3;
			p[1] = 24;
			p[3] = 1;
			p[10] = di.trackspercylinder >> 8;
			p[11] = di.trackspercylinder;
			p[12] = di.bytespersector >> 8;
			p[13] = di.bytespersector;
			p[15] = 1; // interleave
			p[20] = 0x80;
			r[0] += p[1];
		} else if (pcode == 4) {
			if (nodisk (&di))
				goto nodisk;
			p[0] = 4;
			wl(p + 1, di.cylinders);
			p[1] = 24;
			p[5] = 1;
			wl(p + 13, di.cylinders);
			ww(p + 20, 0);
			r[0] += p[1];
		} else if (pcode == 14) { // CD audio control
			uae_u32 vol = sys_command_cd_volume (unitnum, 0xffff, 0xffff);
			p[0] = 0x0e;
			p[1] = 0x0e;
			p[2] = 1;
			p[3] = 4;
			p[6] = 0;
			p[7] = 75;
			p[8] = 1;
			p[9] = pc == 0 ? (vol >> 7) & 0xff : 0xff;
			p[10] = 2;
			p[11] = pc == 0 ? (vol >> (16 + 7)) & 0xff : 0xff;
			r[0] += p[1];
		} else {
			goto err;
		}
		r[0] += r[3];
		scsi_len = lr = r[0] + 1;
	}
	break;
	case 0x1d: /* SEND DIAGNOSTICS */
		scsi_len = 0;
		break;
	case 0x25: /* READ_CAPACITY */
		{
			int pmi = cmdbuf[8] & 1;
			uae_u32 lba = (cmdbuf[2] << 24) | (cmdbuf[3] << 16) | (cmdbuf[4] << 8) | cmdbuf[5];
			int cyl, cylsec, head, tracksec;
			if (nodisk (&di))
				goto nodisk;
			uae_u32 blocks = di.sectorspertrack * di.cylinders * di.trackspercylinder;
			cyl = di.cylinders;
			head = 1;
			cylsec = tracksec = di.trackspercylinder;
			if (pmi == 0 && lba != 0)
				goto errreq;
			if (pmi) {
				lba += tracksec * head;
				lba /= tracksec * head;
				lba *= tracksec * head;
				if (lba > blocks)
					lba = blocks;
				blocks = lba;
			}
			wl (r, blocks);
			wl (r + 4, di.bytespersector);
			scsi_len = lr = 8;
		}
		break;
	case 0x08: /* READ (6) */
	{
		if (nodisk (&di))
			goto nodisk;
		stopplay (unitnum);
		offset = ((cmdbuf[1] & 31) << 16) | (cmdbuf[2] << 8) | cmdbuf[3];
		struct cd_toc *t = gettoc (&di.toc, offset);
		if ((t->control & 0x0c) == 0x04) {
			len = cmdbuf[4];
			if (!len)
				len = 256;
			scsi_len = (uae_u32)cmd_readx (unitnum, scsi_data, offset, len) * di.bytespersector;;
		} else {
			goto notdatatrack;
		}
	}
	break;
	case 0x0a: /* WRITE (6) */
		goto readprot;
	case 0x28: /* READ (10) */
	{
		if (nodisk (&di))
			goto nodisk;
		stopplay (unitnum);
		offset = rl (cmdbuf + 2);
		struct cd_toc *t = gettoc (&di.toc, offset);
		if ((t->control & 0x0c) == 0x04) {
			len = rl (cmdbuf + 7 - 2) & 0xffff;
			scsi_len = cmd_readx (unitnum, scsi_data, offset, len) * di.bytespersector;
		} else {
			goto notdatatrack;
		}
	}
	break;
	case 0x2a: /* WRITE (10) */
		goto readprot;
	case 0xa8: /* READ (12) */
	{
		if (nodisk (&di))
			goto nodisk;
		stopplay (unitnum);
		offset = rl (cmdbuf + 2);
		struct cd_toc *t = gettoc (&di.toc, offset);
		if ((t->control & 0x0c) == 0x04) {
			len = rl (cmdbuf + 6);
			scsi_len = (uae_u32)cmd_readx (unitnum, scsi_data, offset, len) * di.bytespersector;;
		} else {
			goto notdatatrack;
		}
	}
	break;
	case 0xaa: /* WRITE (12) */
		goto readprot;
	case 0x43: // READ TOC
		{
			if (nodisk (&di))
				goto nodisk;
			uae_u8 *p = scsi_data;
			int strack = cmdbuf[6];
			int msf = cmdbuf[1] & 2;
			int format = cmdbuf[2] & 7;
			if (format >= 3)
				goto errreq;
			int maxlen = (cmdbuf[7] << 8) | cmdbuf[8];
			struct cd_toc_head ttoc;
			if (!sys_command_cd_toc (unitnum, &ttoc))
				goto readerr;
			struct cd_toc_head *toc = &ttoc;
			if (maxlen < 4)
				goto errreq;
			if (format == 1) {
				p[0] = 0;
				p[1] = 8;
				p[2] = 1;
				p[3] = 1;
				p[4] = 0;
				p[5] = (toc->toc[0].adr << 4) | toc->toc[0].control;
				p[6] = toc->first_track;
				p[7] = 0;
				if (msf)
					wl (p + 8, lsn2msf (toc->toc[0].address));
				else
					wl (p + 8 , toc->toc[0].address);
				scsi_len = 12;
			} else if (format == 2 || format == 0) {
				if (format == 2 && !msf)
					goto errreq;
				if (strack == 0)
					strack = toc->first_track;
				if (format == 0 && strack >= 100 && strack != 0xaa)
					goto errreq;
				uae_u8 *p2 = p + 4;
				p[2] = 0;
				p[3] = 0;
				maxlen -= 4;
				if (format == 2) {
					if (!addtocentry (&p2, &maxlen, 0xa0, -1, msf, p, toc))
						goto errreq;
					if (!addtocentry (&p2, &maxlen, 0xa1, -1, msf, p, toc))
						goto errreq;
					if (!addtocentry (&p2, &maxlen, 0xa2, -1, msf, p, toc))
						goto errreq;
				}
				while (strack < 100) {
					if (!addtocentry (&p2, &maxlen, strack, -1, msf, p, toc))
						goto errreq;
					strack++;
				}
				if (!addtocentry (&p2, &maxlen, 0xa2, 0xaa, msf, p, toc))
					goto errreq;
				int tlen = p2 - (p + 4);
				p[0] = tlen >> 8;
				p[1] = tlen >> 0;
				scsi_len = tlen + 4;
			}
		}
		break;
		case 0x42: // READ SUB-CHANNEL
		{
			int msf = cmdbuf[1] & 2;
			int subq = cmdbuf[2] & 0x40;
			int format = cmdbuf[3];
			int track = cmdbuf[6];
			int len = rw (cmdbuf + 7);
			uae_u8 buf[SUBQ_SIZE] = { 0 };

			if (nodisk (&di))
				goto nodisk;
			sys_command_cd_qcode (unitnum, buf);
			if (len < 4)
				goto errreq;
			scsi_len = 4;
			scsi_data[0] = 0;
			scsi_data[1] = buf[1];
			if (subq && format == 1) {
				if (len < 4 + 12)
					goto errreq;
				scsi_data[2] = 0;
				scsi_data[3] = 12;
				scsi_len += 12;
				scsi_data[4] = 1;
				scsi_data[5] = (buf[4 + 0] << 4) | (buf[4 + 0] >> 4);
				scsi_data[6] = frombcd (buf[4 + 1]); // track
				scsi_data[7] = frombcd (buf[4 + 2]); // index
				int reladdr = fromlongbcd (&buf[4 + 3]);
				int absaddr = fromlongbcd (&buf[4 + 7]);
				if (!msf) {
					reladdr = msf2lsn (reladdr);
					absaddr = msf2lsn (absaddr);
				}
				wl (scsi_data +  8, absaddr);
				wl (scsi_data + 12, reladdr);
			} else {
				scsi_data[2] = 0;
				scsi_data[3] = 0;
			}
		}
		break;
		case 0x1b: // START/STOP
			sys_command_cd_stop (unitnum);
			scsiemudrv (unitnum, cmdbuf);
			scsi_len = 0;
		break;
		case 0x1e: // PREVENT/ALLOW MEDIA REMOVAL
			// do nothing
			scsi_len = 0;
		break;
		case 0x4e: // STOP PLAY/SCAN
			if (nodisk (&di))
				goto nodisk;
			sys_command_cd_stop (unitnum);
			scsi_len = 0;
		break;
		case 0xba: // SCAN
		{
			if (nodisk (&di))
				goto nodisk;
			struct cd_toc_head ttoc;
			if (!sys_command_cd_toc (unitnum, &ttoc))
				goto readerr;
			struct cd_toc_head *toc = &ttoc;
			int scan = (cmdbuf[1] & 0x10) ? -1 : 1;
			int start = rl (cmdbuf + 1) & 0x00ffffff;
			int end = scan > 0 ? toc->lastaddress : toc->toc[toc->first_track_offset].paddress;
			int type = cmdbuf[9] >> 6;
			if (type == 1)
				start = lsn2msf (start);
			if (type == 3)
				goto errreq;
			if (type == 2) {
				if (toc->first_track_offset + start >= toc->last_track_offset)
					goto errreq;
				start = toc->toc[toc->first_track_offset + start].paddress;
			}
			sys_command_cd_pause (unitnum, 0);
			sys_command_cd_play (unitnum, start, end, scan);
			scsi_len = 0;
		}
		break;
		case 0x48: // PLAY AUDIO TRACK/INDEX
		{
			if (nodisk (&di))
				goto nodisk;
			int strack = cmdbuf[4];
			int etrack = cmdbuf[7];
			struct cd_toc_head ttoc;
			if (!sys_command_cd_toc (unitnum, &ttoc))
				goto readerr;
			struct cd_toc_head *toc = &ttoc;
			if (strack < toc->first_track || strack > toc->last_track ||
				etrack < toc->first_track || etrack > toc->last_track ||
				strack > etrack)
				goto errreq;
			int start = toc->toc[toc->first_track_offset + strack - 1].paddress;
			int end = etrack == toc->last_track ? toc->lastaddress : toc->toc[toc->first_track_offset + etrack - 1 + 1].paddress;
			sys_command_cd_pause (unitnum, 0);
			if (!sys_command_cd_play (unitnum, start, end, 0))
				goto notdatatrack;
			scsi_len = 0;
		}
		break;
		case 0x49: // PLAY AUDIO TRACK RELATIVE (10)
		case 0xa9: // PLAY AUDIO TRACK RELATIVE (12)
		{
			if (nodisk (&di))
				goto nodisk;
			int len = cmd == 0xa9 ? rl (cmdbuf + 6) : rw (cmdbuf + 7);
			int track = cmd == 0xa9 ? cmdbuf[10] : cmdbuf[6];
			if (track < di.toc.first_track || track > di.toc.last_track)
				goto errreq;
			int start = di.toc.toc[di.toc.first_track_offset + track - 1].paddress;
			int rel = rl (cmdbuf + 2);
			start += rel;
			int end = start + len;
			if (end > di.toc.lastaddress)
				end = di.toc.lastaddress;
			if (len > 0) {
				sys_command_cd_pause (unitnum, 0);
				if (!sys_command_cd_play (unitnum, start, start + len, 0))
					goto notdatatrack;
			}
			scsi_len = 0;
		}
		break;
		case 0x47: // PLAY AUDIO MSF
		{
			if (nodisk (&di))
				goto nodisk;
			int start = rl (cmdbuf + 2) & 0x00ffffff;
			if (start == 0x00ffffff) {
				uae_u8 buf[SUBQ_SIZE] = { 0 };
				sys_command_cd_qcode (unitnum, buf);
				start = fromlongbcd (buf + 4 + 7);
			}
			int end = msf2lsn (rl (cmdbuf + 5) & 0x00ffffff);
			if (end > di.toc.lastaddress)
				end = di.toc.lastaddress;
			start = msf2lsn (start);
			if (start > end)
				goto errreq;
			if (start < end)
				sys_command_cd_pause (unitnum, 0);
				if (!sys_command_cd_play (unitnum, start, end, 0))
					goto notdatatrack;
			scsi_len = 0;
		}
		break;
		case 0x45: // PLAY AUDIO (10)
		case 0xa5: // PLAY AUDIO (12)
		{
			if (nodisk (&di))
				goto nodisk;
			int start = rl (cmdbuf + 2);
			int len;
			if (cmd = 0xa5)
				len = rl (cmdbuf + 6);
			else
				len = rw (cmdbuf + 7);
			if (len > 0) {
				if (start == -1) {
					uae_u8 buf[SUBQ_SIZE] = { 0 };
					sys_command_cd_qcode (unitnum, buf);
					start = msf2lsn (fromlongbcd (buf + 4 + 7));
				}
				int end = start + len;
				if (end > di.toc.lastaddress)
					end = di.toc.lastaddress;
				sys_command_cd_pause (unitnum, 0);
				if (!sys_command_cd_play (unitnum, start, end, 0))
					goto notdatatrack;
			}
			scsi_len = 0;
		}
		break;
		case 0xbc: // PLAY CD
		{
			if (nodisk (&di))
				goto nodisk;
			int start = -1;
			int end = -1;
			if (cmdbuf[1] & 2) {
				start = msf2lsn (rl (cmdbuf + 2) & 0x00ffffff);
				end = msf2lsn (rl (cmdbuf + 5) & 0x00ffffff);
			} else {
				start = rl (cmdbuf + 2);
				end = start + rl (cmdbuf + 6);
			}
			if (end > di.toc.lastaddress)
				end = di.toc.lastaddress;
			if (start > end)
				goto errreq;
			if (start < end) {
				sys_command_cd_pause (unitnum, 0);
				if (!sys_command_cd_play (unitnum, start, end, 0))
					goto notdatatrack;
			}
		}
		break;
		case 0x4b: // PAUSE/RESUME
		{
			if (nodisk (&di))
				goto nodisk;
			uae_u8 buf[SUBQ_SIZE] = { 0 };
			int resume = cmdbuf[8] & 1;
			sys_command_cd_qcode (unitnum, buf);
			if (buf[1] != AUDIO_STATUS_IN_PROGRESS && buf[1] != AUDIO_STATUS_PAUSED)
				goto errreq;
			sys_command_cd_pause (unitnum, resume ? 0 : 1);
			scsi_len = 0;
		}
		break;
readprot:
		status = 2; /* CHECK CONDITION */
		s[0] = 0x70;
		s[2] = 7; /* DATA PROTECT */
		s[12] = 0x27; /* WRITE PROTECTED */
		ls = 12;
		break;
nodisk:
		status = 2; /* CHECK CONDITION */
		s[0] = 0x70;
		s[2] = 2; /* NOT READY */
		s[12] = 0x3A; /* MEDIUM NOT PRESENT */
		ls = 12;
		break;
readerr:
		status = 2; /* CHECK CONDITION */
		s[0] = 0x70;
		s[2] = 2; /* NOT READY */
		s[12] = 0x11; /* UNRECOVERED READ ERROR */
		ls = 12;
		break;
notdatatrack:
		status = 2;
		s[0] = 0x70;
		s[2] = 5;
		s[12] = 0x64; /* ILLEGAL MODE FOR THIS TRACK */
		ls = 12;
		break;

	default:
err:
		write_log (L"CDEMU: unsupported scsi command 0x%02X\n", cmdbuf[0]);
errreq:
		lr = -1;
		status = 2; /* CHECK CONDITION */
		s[0] = 0x70;
		s[2] = 5; /* ILLEGAL REQUEST */
		s[12] = 0x24; /* ILLEGAL FIELD IN CDB */
		ls = 12;
		break;
	}
end:
	*data_len = scsi_len;
	*reply_len = lr;
	*sense_len = ls;
	if (cmdbuf[0] && log_scsiemu)
		write_log (L"-> DATAOUT=%d ST=%d SENSELEN=%d\n", scsi_len, status, ls);
	return status;
}

static int execscsicmd_direct (int unitnum, struct amigascsi *as)
{
	int sactual = 0;
	int io_error = 0;
	uae_u8 *scsi_datap, *scsi_datap_org;
	uae_u32 scsi_cmd_len_orig = as->cmd_len;
	uae_u8 cmd[16] = { 0 };
	uae_u8 replydata[256];
	int datalen = as->len;
	int senselen = as->sense_len;
	int replylen = 0;

	memcpy (cmd, as->cmd, as->cmd_len);
	scsi_datap = scsi_datap_org = as->len ? as->data : 0;
	if (as->sense_len > 32)
		as->sense_len = 32;

	as->status = scsi_emulate (unitnum, cmd, as->cmd_len, scsi_datap, &datalen, replydata, &replylen, as->sensedata, &senselen);

	as->cmdactual = as->status != 0 ? 0 : as->cmd_len; /* fake scsi_CmdActual */
	if (as->status) {
		io_error = IOERR_BadStatus;
		as->sense_len = senselen;
		as->actual = 0; /* scsi_Actual */
	} else {
		int i;
		if (replylen > 0) {
			for (i = 0; i < replylen; i++)
				scsi_datap[i] = replydata[i];
			datalen = replylen;
		}
		for (i = 0; i < as->sense_len; i++)
			as->sensedata[i] = 0;
		sactual = 0;
		if (datalen < 0) {
			io_error = IOERR_NotSpecified;
			as->actual = 0; /* scsi_Actual */
		} else {
			as->len = datalen;
			io_error = 0;
			as->actual = as->len; /* scsi_Actual */
		}
	}
	as->sactual = sactual;

	return io_error;
}

int sys_command_scsi_direct_native (int unitnum, struct amigascsi *as)
{
	if (scsiemu[unitnum]) {
		return execscsicmd_direct (unitnum, as);
	} else {
		if (!device_func[unitnum]->exec_direct)
			return -1;
	}
	int ret = device_func[unitnum]->exec_direct (unitnum, as);
	if (!ret && device_func[unitnum]->isatapi(unitnum))
		scsi_atapi_fixup_inquiry (as);
	return ret;
}

int sys_command_scsi_direct (int unitnum, uaecptr acmd)
{
	int ret, i;
	struct amigascsi as;
	uaecptr ap;
	addrbank *bank;

	ap = get_long (acmd + 0);
	as.len = get_long (acmd + 4);

	bank = &get_mem_bank (ap);
	if (!bank || !bank->check(ap, as.len))
		return IOERR_BADADDRESS;
	as.data = bank->xlateaddr (ap);

	ap = get_long (acmd + 12);
	as.cmd_len = get_word (acmd + 16);
	if (as.cmd_len > sizeof as.cmd)
		return IOERR_BADLENGTH;
	for (i = 0; i < as.cmd_len; i++)
		as.cmd[i] = get_byte (ap++);
	while (i < sizeof as.cmd)
		as.cmd[i++] = 0;
	as.flags = get_byte (acmd + 20);
	as.sense_len = get_word (acmd + 26);

	ret = sys_command_scsi_direct_native (unitnum, &as);

	put_long (acmd + 8, as.actual);
	put_word (acmd + 18, as.cmdactual);
	put_byte (acmd + 21, as.status);
	put_word (acmd + 28, as.sactual);

	ap = get_long (acmd + 22);
	for (i = 0; i < as.sactual; i++)
		put_byte (ap, as.sensedata[i]);

	return ret;
}

#ifdef SAVESTATE

uae_u8 *save_cd (int num, int *len)
{
	uae_u8 *dstbak, *dst;

	memset(play_qcode[num], 0, SUBQ_SIZE);
	if (!currprefs.cdslots[num].inuse || num >= MAX_TOTAL_SCSI_DEVICES)
		return NULL;
	if (!currprefs.cs_cd32cd && !currprefs.cs_cdtvcd && !currprefs.scsi)
		return NULL;
	dstbak = dst = xmalloc (uae_u8, 4 + 256 + 4 + 4);
	save_u32 (4 | 8);
	save_path (currprefs.cdslots[num].name, SAVESTATE_PATH_CD);
	save_u32 (currprefs.cdslots[num].type);
	save_u32 (0);
	save_u32 (0);
	sys_command_cd_qcode (num, play_qcode[num]);
	for (int i = 0; i < SUBQ_SIZE; i++)
		save_u8 (play_qcode[num][i]);
	save_u32 (play_end_pos[num]);
	*len = dst - dstbak;
	return dstbak;
}

uae_u8 *restore_cd (int num, uae_u8 *src)
{
	uae_u32 flags;
	TCHAR *s;

	if (num >= MAX_TOTAL_SCSI_DEVICES)
		return NULL;
	flags = restore_u32 ();
	s = restore_path (SAVESTATE_PATH_CD);
	int type = restore_u32 ();
	restore_u32 ();
	if (flags & 4) {
		_tcscpy (changed_prefs.cdslots[num].name, s);
		_tcscpy (currprefs.cdslots[num].name, s);
		changed_prefs.cdslots[num].type = currprefs.cdslots[num].type = type;
	}
	if (flags & 8) {
		restore_u32 ();
		for (int i = 0; i < SUBQ_SIZE; i++)
			play_qcode[num][i] = restore_u8 ();
		play_end_pos[num] = restore_u32 ();
	}
	return src;
}

#endif

